/*
 * Copyright (c) 2013, Tomas Zima
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 * 
 * 1) Redistributions of source code must retain the above copyright notice, this list
 *    of conditions and the following disclaimer.
 * 2) Redistributions in binary form must reproduce the above copyright notice, this list 
 *    of conditions and the following disclaimer in the documentation and/or other materials 
 *    provided with the distribution.
 * 3) Neither the name of the Tomas Zima nor the names of its contributors may be used to
 *    endorse or promote products derived from this software without specific prior written
 *    permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package jgamio.framework.application;

import org.lwjgl.input.Keyboard;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Stack;

/**
 * <p>
 * Class for handling of inputs from keyboard.
 * </p>
 * 
 * <p>
 * You can define the event number, which will be
 * generated after the press of some key. It is
 * controlled specifically for each component
 * in the game.
 * </p>
 *  
 * @author Tomas Zima
 */
public class KeyboardHandler implements InputHandler
{
	/**
	 * Contains keyboard map for each component.
	 */
	private Hashtable<Component, Stack<EventDescription>> keyboardMap = new Hashtable<Component, Stack<EventDescription>>();
	
	/** Identifier of active component. */
	private Component						activeComponent	= null;
	
	/** Contains all listeners. */
	private List<Listener>					listeners		= new ArrayList<Listener>();
	
	/**
	 * Sets active component.
	 */
	public void setActiveComponent(Component component)
	{
		this.activeComponent = component;
	}
	
	/**
	 * Add a new key, which should be watched (and how) for game,
	 * and number of event, which will be generated in the case of press.
	 * 
	 * @param component		Unique identifier of a component.
	 * @param key			Number of key from LWJGL: Keyboard.KEY_*.
	 * @param eventNumber	Your number of event. It should be unique.
	 * @param type			Type of key press (single / repeated).
	 */
	public void addEvent(Component component, int key, KeyEvent event, KeyPressType type)
	{
		if (!keyboardMap.containsKey(component))
		{
			keyboardMap.put(component, new Stack<EventDescription>());
		}
		
		keyboardMap.get(component).push(new EventDescription(key, event, type));
	}
	
	/**
	 * <p>
	 * This should be called in each iteration of the game loop.
	 * It will check if some key was pressed. If so, it will generate
	 * new event according to the event number, which was previously set
	 * up.
	 * </p>
	 * 
	 * <p>
	 * The method itself will just check inputs and generate events,
	 * nothing more.
	 * </p>
	 */
	@Override
	public void handleInputs()
	{
		/*
		 * Pick up next keyboard event. 
		 */
		while (Keyboard.next())
		{
			/*
			 * Iterates trough the whole menu keyboard map.
			 * If some defined key was pressed, it will generate
			 * new event.
			 */			
			for (int i = 0; i < keyboardMap.get(activeComponent).size(); i++)
			{				
				// Checks, which key caused the current event.
				if (Keyboard.getEventKey() == keyboardMap.get(activeComponent).get(i).key)
				{
					// Was key pressed?
					if (Keyboard.getEventKeyState())
					{
						// Is it requested press type (single)?
						if (keyboardMap.get(activeComponent).get(i).keyPressType == KeyPressType.SINGLE)
						{
							triggerListener(keyboardMap.get(activeComponent).get(i).event);
						}
					}
					// Was key released?
					else
					{
						// Is it requested press type (released)?
						if (keyboardMap.get(activeComponent).get(i).keyPressType == KeyPressType.RELEASED)
						{
							triggerListener(keyboardMap.get(activeComponent).get(i).event);
						}
					}
				}
			}
		}

		/*
		 * Iterates trough the whole menu keyboard map.
		 * If some defined key was pressed, it will generate
		 * new event.
		 */
		for (int i = 0; i < keyboardMap.get(activeComponent).size(); i++)
		{			
			/*
			 * Key presses SINGLE and RELEASED was handled before. This will
			 * handle only type REPEATED.
			 */
			if (Keyboard.isKeyDown(keyboardMap.get(activeComponent).get(i).key))
			{
				if (keyboardMap.get(activeComponent).get(i).keyPressType == KeyPressType.REPEATED)
				{
					triggerListener(keyboardMap.get(activeComponent).get(i).event);
				}
			}
		}
	}
	
	/**
	 * Adds new listener.
	 * 
	 * @param listener New listener.
	 */
	public final void addListener(Listener listener)
	{
		this.listeners.add(listener);
	}
	
	/**
	 * Calls listeners.
	 * 
	 * @param comp		Component which generated the event.
	 * @param event		User defined event message.
	 */
	private void triggerListener(KeyEvent event)
	{
		for (int i = 0; i < this.listeners.size(); i++)
		{
			this.listeners.get(i).onKeyEvent(event);
		}
	}
	
	/**
	 * Returns value of key by its name.
	 * 
	 * @param name		Name of key (for example "LEFT", "RIGHT").
	 * @return			LWJGL value of the key.
	 */
	public static int getKeyValue(String name)
	{
		return Keyboard.getKeyIndex(name);
	}
	
	/**
	 * <p>
	 * Represents all possible type of key presses.
	 * </p>
	 * 
	 * <p>
	 * The point is, that LWJGL will generate new
	 * events during the whole time of press. However,
	 * in some cases, you want to generate only one
	 * event per press.
	 * </p>
	 * 
	 * @author Tomas Zima
	 */
	public static enum KeyPressType
	{
		/**
		 * KeyboardHandler will generate only one event
		 * if you press some key. Another event will be
		 * generated when you leave this key and press
		 * it again.
		 */
		SINGLE,
		
		/**
		 * KeyboardHandler will generate new events for
		 * the whole time of press. This is typically used,
		 * for example, for movement of player.
		 */
		REPEATED,
		
		/**
		 * KeyboardHandler will generate new event when
		 * the key is released.
		 */
		RELEASED,
	}
	
	/**
	 * Represents event generated by key. It's chosen by user.
	 * 
	 * @author Tomas Zima
	 */
	public class KeyEvent
	{
		/** Value of an event. */
		private ClientEnumParent<?> value;
		
		/**
		 * Constructor.
		 * Sets value of events.
		 */
		public KeyEvent(ClientEnumParent<?> value)
		{
			this.value = value;
		}

		/**
		 * Returns value of an events.
		 */
		public ClientEnumParent<?> getValue()
		{
			return this.value;
		}
	}
	
	/**
	 * This interface have to be implemented by each listener.
	 * 
	 * @author Tomas Zima
	 */
	public interface Listener
	{
		public void onKeyEvent(KeyEvent eventNumber);
	}
}
